package jp.co.nes.awssupport.report.editor;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.CampaignAssignedBean;
import jp.co.nes.awssupport.common.bean.MiscellaneousExpensesAssignedBean;
import jp.co.nes.awssupport.common.bean.MonthlyCompiledBillingBean;
import jp.co.nes.awssupport.common.bean.UsageDetailsBean;
import jp.co.nes.awssupport.common.utility.PoiUtil;
import jp.co.nes.awssupport.common.utility.ReplaceHelper;

/**
 * ご利用明細編集クラスです。<br>
 * ご利用明細テンプレートに埋め込まれている置換パラメータを置換します。
 */
public class UsageReportEditor implements IUsageReportEditor {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(UsageReportEditor.class);

    /** 置換処理ヘルパー */
    private static ReplaceHelper<UsageDetailsBean> replaceHelper = new ReplaceHelper<>();

    /**
     * 複数行(明細部分、キャンペーン部分、雑費部分)になるもの以外の置換処理に関するマップを登録します。
     */
    static {
        replaceHelper.addForBean("#DOCUMENT_TITLE#");
        replaceHelper.addForBean("#PUBLISH_DATE#", "yyyy年MM月dd日");
        replaceHelper.addForBean("#COMPANY#");
        replaceHelper.addForBean("#DEPARTMENT#");
        replaceHelper.addForBean("#TITLE#");
        replaceHelper.addForBean("#NAME#");
        replaceHelper.addForBean("#ACCOUNT_ID#");
        replaceHelper.addForBean("#RATE#");
        replaceHelper.addForFixed("#DETAILS_NO#", "No.");
        replaceHelper.addForFixed("#DETAILS_PRODUCT_NAME#", "製品名");
        replaceHelper.addForFixed("#DETAILS_USAGE_TYPE_NAME#", "ご使用タイプ");
        replaceHelper.addForFixed("#DETAILS_USAGE_QUANTITY#", "ご使用量");
        replaceHelper.addForFixed("#DETAILS_RATE#", "適用料金");
        replaceHelper.addForFixed("#DETAILS_AMOUNT#", "ご使用料");
        replaceHelper.addForBean("#SUM_AMOUNT_1#");
        replaceHelper.addForBean("#CREDIT_COUPON_AMOUNT#");
        replaceHelper.addForBean("#PREMIUM_SUPPORT_TARGET_AMOUNT#", "サポート料金対象額：\\%,d");
        replaceHelper.addForBean("#PREMIUM_SUPPORT_AMOUNT#");
        replaceHelper.addForBean("#SUM_AMOUNT_2#");
    }

    /** キャンペーン部分の名称に関する置換パラメータ */
    // 本パラメータは複数箇所で使用するので定義した(1箇所でしか使用していないものは定義していない)。
    private static final String CAMPAIGN = "#ASSIGNED_CAMPAIGN_NAME#";

    /** 雑費部分の名称に関する置換パラメータ */
    // 本パラメータは複数箇所で使用するので定義した(1箇所でしか使用していないものは定義していない)。
    private static final String MISCELLANEOUS_EXPENSES = "#ASSIGNED_MISCELLANEOUS_EXPENSES_NAME#";

    /**
     * 従量利用分、リザーブド購入分、その他(ドメイン購入分など)に関する明細タイトル行を表すパラメータ<br>
     * 1つ目が従量利用分、2つ目がリザーブド購入分、3つ目がその他(ドメイン購入分など)です。
     */
    // 本パラメータは複数箇所で使用するので定義した(1箇所でしか使用していないものは定義していない)。
    private static final String[] DETAILS_TITLE = { "#DETAILS1_TITLE_START#", "#DETAILS2_TITLE_START#",
            "#DETAILS3_TITLE_START#" };

    /**
     * 従量利用分、リザーブド購入分、その他(ドメイン購入分など)に関する明細開始行を表すパラメータ<br>
     * 1つ目が従量利用分、2つ目がリザーブド購入分、3つ目がその他(ドメイン購入分など)です。
     */
    // 本パラメータは複数箇所で使用するので定義した(1箇所でしか使用していないものは定義していない)。
    private static final String[] DETAILS_START = { "#DETAILS1_START#", "#DETAILS2_START#", "#DETAILS3_START#" };

    /** ご利用明細(出力する内容) */
    private UsageDetailsBean usageDetailsBean;

    /** シート(Excelシート) */
    private Sheet sheet;

    /** 明細タイトルのセル結合情報 */
    private Map<Integer, Integer> detailsTitleMergedMap;

    /** 明細のセル結合情報 */
    private Map<Integer, Integer> detailsMergedMap;

    /**
     * デフォルトコンストラクタです。
     */
    public UsageReportEditor() {
    }

    /*
     * (非 Javadoc)
     * @see jp.co.nes.awssupport.report.editor.IUsageReportEditor#edit(org.apache.poi.ss.usermodel.Workbook, jp.co.nes.awssupport.report.bean.UsageDetailsBean)
     */
    @Override
    public void edit(Workbook workbook, UsageDetailsBean usageDetailsBean) {
        replaceHelper.setBean(usageDetailsBean);

        this.usageDetailsBean = usageDetailsBean;
        this.sheet = workbook.getSheetAt(0);

        // 明細タイトルと明細のセル結合情報の設定
        detailsTitleMergedMap = PoiUtil.getMergedMap(sheet, getFirstRow(DETAILS_TITLE[0]).getRowNum());
        detailsMergedMap = PoiUtil.getMergedMap(sheet, getFirstRow(DETAILS_START[0]).getRowNum());

        // 不要行の削除処理
        deleteUnnecessaryRow();

        // 複数行(明細部分、キャンペーン部分、雑費部分)になるもの以外の置換処理
        replace();

        // 複数行(明細部分、キャンペーン部分、雑費部分)になるものの置換処理
        replaceMultiple();

        // 明細開始行や明細タイトル行を表すパラメータを削除
        List<String> list = new ArrayList<>();
        list.addAll(Arrays.asList(DETAILS_TITLE));
        list.addAll(Arrays.asList(DETAILS_START));
        for (String value : list) {
            Entry<Row, Cell> rowCellEntry = PoiUtil.getFirstRowCell(sheet, value);
            if (rowCellEntry != null) {
                rowCellEntry.getValue().setCellValue("");
            }
        }
    }

    /**
     * 引数の内容が含まれている最初のRowを返却します。<br>
     * なお、該当するセルが存在しない場合は、CommonException例外がスローされます。
     *
     * @param value セルに含まれている内容
     * @return Row
     */
    private Row getFirstRow(String value) {
        Entry<Row, Cell> rowCellEntry = PoiUtil.getFirstRowCell(sheet, value);
        if (rowCellEntry == null) {
            // 本クラスの中では、valueには存在するものしか引き渡されないので、通常は発生しない。
            throw new CommonException("row number get fail");
        }
        return rowCellEntry.getKey();
    }

    /**
     * 不要行の削除処理です。
     */
    private void deleteUnnecessaryRow() {
        List<MonthlyCompiledBillingBean> meteredList = usageDetailsBean.getMonthlyCompiledBillingBeans();
        if (meteredList.isEmpty()) {
            // 従量利用分がなかった場合。
            PoiUtil.deleteRow(sheet, getFirstRow(DETAILS_TITLE[0]), detailsTitleMergedMap);
            PoiUtil.deleteRow(sheet, getFirstRow(DETAILS_START[0]), detailsMergedMap);
        }

        List<MonthlyCompiledBillingBean> reservedList = usageDetailsBean.getMonthlyCompiledBillingBeansOfReserved();
        if (reservedList.isEmpty()) {
            // リザーブド購入分がなかった場合。
            PoiUtil.deleteRow(sheet, getFirstRow(DETAILS_TITLE[1]), detailsTitleMergedMap);
            PoiUtil.deleteRow(sheet, getFirstRow(DETAILS_START[1]), detailsMergedMap);
        }

        List<MonthlyCompiledBillingBean> otherList = usageDetailsBean.getMonthlyCompiledBillingBeansOfOther();
        if (otherList.isEmpty()) {
            // その他(ドメイン購入分など)がなかった場合。
            PoiUtil.deleteRow(sheet, getFirstRow(DETAILS_TITLE[2]), detailsTitleMergedMap);
            PoiUtil.deleteRow(sheet, getFirstRow(DETAILS_START[2]), detailsMergedMap);
        }

        List<CampaignAssignedBean> caList = usageDetailsBean.getCampaignAssignedList();
        if (caList.isEmpty()) {
            // キャンペーンがなかった場合
            PoiUtil.deleteRow(sheet, getFirstRow(CAMPAIGN), detailsMergedMap);
        }

        List<MiscellaneousExpensesAssignedBean> meaList = usageDetailsBean.getMiscellaneousExpensesAssignedList();
        if (meaList.isEmpty()) {
            // 雑費がなかった場合
            PoiUtil.deleteRow(sheet, getFirstRow(MISCELLANEOUS_EXPENSES), detailsMergedMap);
        }

        if (!usageDetailsBean.isCreditCouponAmountExist()) {
            // クレジット適用額がなかった場合
            PoiUtil.deleteRow(sheet, getFirstRow("#CREDIT_COUPON_AMOUNT#"), detailsMergedMap);
        }

        if (!usageDetailsBean.isPremiumSupportAmountExist()) {
            // プレミアムサポート適用額がなかった場合
            PoiUtil.deleteRow(sheet, getFirstRow("#PREMIUM_SUPPORT_AMOUNT#"), detailsMergedMap);
        }
    }

    /**
     * 複数行(明細部分、キャンペーン部分、雑費部分)になるもの以外の置換処理です。
     */
    private void replace() {
        for (Iterator<Row> rowIterator = sheet.iterator(); rowIterator.hasNext();) {
            Row row = rowIterator.next();
            for (Iterator<Cell> cellIterator = row.iterator(); cellIterator.hasNext();) {
                Cell cell = cellIterator.next();

                if (cell.getCellType() != Cell.CELL_TYPE_STRING) {
                    continue;
                }

                String cellValue = cell.getStringCellValue();

                if (!cellValue.startsWith("#") || !cellValue.endsWith("#")) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("continue(cellValue=" + "'" + cellValue + "')");
                    }
                    continue;
                }

                Object obj = replaceHelper.getReplaceValue(cellValue);
                if (obj == null) {
                    // 置換パラメータが登録されていない場合(通常は発生しない)。
                    // この場合は何もしない。なお、Beanから取得した値がnullだった場合は、空文字列に変換されて返却されている。
                } else if (obj instanceof String) {
                    cell.setCellValue((String) obj);
                } else if (obj instanceof Long) {
                    cell.setCellValue(((Long) obj).longValue());
                } else if (obj instanceof BigDecimal) {
                    cell.setCellValue(((BigDecimal) obj).doubleValue());
                } else {
                    // 通常は発生しない
                    throw new CommonException("replace fail(replaceParameter=" + cellValue + ")");
                }
            }
        }
    }

    /**
     *  複数行(明細部分、キャンペーン部分、雑費部分)になるものの置換処理です。
     */
    private void replaceMultiple() {
        // 従量利用分の処理
        List<MonthlyCompiledBillingBean> meteredList = usageDetailsBean.getMonthlyCompiledBillingBeans();
        replaceMultiple(meteredList, DETAILS_START[0]);

        // リザーブド購入分の処理
        List<MonthlyCompiledBillingBean> reservedList = usageDetailsBean.getMonthlyCompiledBillingBeansOfReserved();
        replaceMultiple(reservedList, DETAILS_START[1]);

        // その他(ドメイン購入分など)の処理
        List<MonthlyCompiledBillingBean> otherList = usageDetailsBean.getMonthlyCompiledBillingBeansOfOther();
        replaceMultiple(otherList, DETAILS_START[2]);

        // キャンペーン部分の処理
        List<CampaignAssignedBean> caList = usageDetailsBean.getCampaignAssignedList();
        replaceMultiple(caList, new String[] { CAMPAIGN, "#CAMPAIGN_AMOUNT#" });

        // 雑費部分の処理
        List<MiscellaneousExpensesAssignedBean> meaList = usageDetailsBean.getMiscellaneousExpensesAssignedList();
        replaceMultiple(meaList, new String[] { MISCELLANEOUS_EXPENSES, "#MISCELLANEOUS_EXPENSES_AMOUNT#" });
    }

    /**
     * 明細部分の置換処理です。
     *
     * @param list 明細に関するリスト
     * @param startRowParameter 明細開始行を示すパラメータ
     */
    private void replaceMultiple(List<MonthlyCompiledBillingBean> list, String startRowParameter) {
        if (list.isEmpty()) {
            return;
        }

        int detailsNo = 0;
        Row startRow = getFirstRow(startRowParameter);
        int rowNum = startRow.getRowNum();
        boolean isStart = true;

        if (logger.isDebugEnabled()) {
            logger.debug("startRowNum=" + rowNum);
            list.forEach(bean -> logger.debug(bean.toString()));
        }

        for (MonthlyCompiledBillingBean bean : list) {
            if (isStart) {
                isStart = false;
            } else {
                PoiUtil.insertRow(sheet, sheet.getRow(rowNum), detailsMergedMap);
                rowNum++;
            }

            detailsNo++;

            int count = 0;
            List<Integer> cellNumList = detailsMergedMap.keySet().stream().sorted(Comparator.naturalOrder())
                    .collect(Collectors.toList());
            for (int cellNum : cellNumList) {
                Row row = sheet.getRow(rowNum);
                Cell cell = row.getCell(cellNum);

                if (count == 0) {
                    cell.setCellValue(detailsNo);
                } else if (count == 1) {
                    // 製品名は、月次集計結果テーブルでnull設定が不可なので、nullが設定されることはない。
                    // よって、null判断は実施していない。
                    cell.setCellValue(bean.getProductName());
                } else if (count == 2) {
                    // 使用タイプ名には月次集計結果テーブルの説明を設定する。
                    // また、説明は、月次集計結果テーブルでnull設定が可能なので、nullが設定されることがある。
                    // よって、null判断は実施している(nullだった場合は空文字列にして出力する)。
                    String usageTypeName = bean.getDescription();
                    if (usageTypeName == null) {
                        usageTypeName = "";
                    }
                    cell.setCellValue(usageTypeName);
                } else if (count == 3) {
                    // 使用量は、月次集計結果テーブルでnull設定が不可なので、nullが設定されることはない。
                    // よって、null判断は実施していない。
                    // なお、データ取得時、小数第9位で四捨五入された結果がBeanに設定されている前提。
                    cell.setCellValue(bean.getUsageQuantity().doubleValue());
                } else if (count == 4) {
                    // 適用レートは、月次集計結果テーブルでnull設定が不可なので、nullが設定されることはない。
                    // よって、null判断は実施していない。
                    // なお、データ取得時、小数第9位で四捨五入された結果がBeanに設定されている前提。
                    cell.setCellValue(bean.getRate().doubleValue());
                } else if (count == 5) {
                    // 請求額（税抜）は、月次集計結果テーブルでnull設定が不可なので、nullが設定されることはない。
                    // よって、null判断は実施していない。
                    // なお、データ取得時、小数第4位で四捨五入された結果がBeanに設定されている前提。
                    cell.setCellValue(bean.getBillingAmount().doubleValue());
                } else {
                    // 通常は発生しない。
                    throw new CommonException("replace multiple fail");
                }
                count++;
            }
        }
    }

    /**
    * キャンペーン部分、雑費部分の置換処理です。
    *
    * @param list キャンペーン部分 or 雑費部分のリスト
    * @param replaceParameterArray キャンペーン部分 or 雑費部分のパラメータ配列(1つ目が名称の置換パラメータ、2つ目が金額の置換パラメータ)
    */
    private <T> void replaceMultiple(List<T> list, String[] replaceParameterArray) {
        if (list.isEmpty()) {
            return;
        }

        Row startRow = getFirstRow(replaceParameterArray[0]);
        int rowNum = startRow.getRowNum();
        boolean isStart = true;

        if (logger.isDebugEnabled()) {
            logger.debug("startRowNum=" + rowNum);
            list.forEach(bean -> logger.debug(bean.toString()));
        }

        for (T bean : list) {
            if (isStart) {
                isStart = false;
            } else {
                PoiUtil.insertRow(sheet, sheet.getRow(rowNum), detailsMergedMap);
                rowNum++;
            }

            int count = 0;
            List<Integer> cellNumList = detailsMergedMap.keySet().stream().sorted(Comparator.naturalOrder())
                    .collect(Collectors.toList());
            for (int cellNum : cellNumList) {
                Row row = sheet.getRow(rowNum);
                Cell cell = row.getCell(cellNum);

                if (count == 0 || count == 2 || count == 3 || count == 4) {
                    // 何もしない。
                } else if (count == 1) {
                    // 適用キャンペーン名、適用雑費名は、それぞれの適用テーブルでnull設定が可能なので、nullが設定されることがある。
                    // よって、null判断は実施している(nullだった場合は空文字列にして出力する)。
                    String name = (String) ReplaceHelper.get(bean, replaceParameterArray[0]);
                    if (name == null) {
                        name = "";
                    }
                    cell.setCellValue(String.format("*** %s ***", name));
                } else if (count == 5) {
                    // キャンペーン適用額、雑費適用額は、それぞれの適用テーブルでnull設定が不可なので、nullが設定されることはない。
                    // よって、null判断は実施していない。
                    Object obj = ReplaceHelper.get(bean, replaceParameterArray[1]);
                    long amount = ((Long) obj).longValue();
                    cell.setCellValue(amount);
                } else {
                    // 通常は発生しない。
                    throw new CommonException("replace multiple fail");
                }
                count++;
            }
        }
    }
}
